package priv.wjh.study.cloud.mock.feign;

import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.lang.NonNullApi;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.util.*;

/**
 * //TODO
 *
 * @author wangjunhao
 **/
@ConditionalOnProperty(prefix = "me", name = "mock", havingValue = "true")
@Component
public class MyMock implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware {

    private static final Set<String> BASE_PACKAGES = new HashSet<>();
    private static final Set<String> EXCLUDE_CLASS = new HashSet<>();

    static {
        BASE_PACKAGES.add("priv.wjh.study.cloud.feign.api");
//        EXCLUDE_CLASS.add("Api");
    }

    private static final Logger logger = LoggerFactory.getLogger(MyMock.class);

    private ResourceLoader resourceLoader;
    private Environment environment;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        logger.info("Mock bean start");

        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));

        for (String basePackage : BASE_PACKAGES) {
            candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
        }

        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                registerMockBean(registry, candidateComponent);
            }
        }
    }

    private void registerMockBean(BeanDefinitionRegistry registry, BeanDefinition candidateComponent) {
        // filter exclude class
        String className = Objects.requireNonNull(candidateComponent.getBeanClassName());
        for (String excludeClass : EXCLUDE_CLASS) {
            if (className.endsWith(excludeClass)) {
                return;
            }
        }

        try {
            registry.removeBeanDefinition(className);
        } catch (NoSuchBeanDefinitionException e) {
            e.printStackTrace();
            return;
        }

        Class clazz = ClassUtils.resolveClassName(className, null);
        // register mock bean
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> Mockito.mock(clazz));
        registry.registerBeanDefinition(candidateComponent.getBeanClassName(),
                                        beanDefinitionBuilder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata()
                        .isIndependent()) {
                    if (!beanDefinition.getMetadata()
                            .isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

